2dspere index in {mongodb}

mongodb에서 $geoNear를 사용하여 검색을 하기 위해선 반드시 2d 혹은 2dsphere 인덱스가 정해진 컬럼에 포함되어야 합니다.


아래 문서는 MongoDB에서 제공하는 2dsphere 인덱스(지구 표면을 근사하여 구면 상의 위치 정보를 지원하기 위한 인덱스)에 대해, 지리적 쿼리에 사용되는 $geoNear 명령(Aggregation Pipeline 단계)까지 포함해 한국어로 정리한 문서입니다. 공식 문서( MongoDB 공식 Geospatial Queries )를 바탕으로 이해하기 쉽게 재구성했습니다.


MongoDB 2dsphere 인덱스 개요

MongoDB는 지리 공간 쿼리(Geospatial Query)를 지원하기 위해 두 가지 종류의 지리 공간 인덱스를 제공하며, 그 중 하나가 2dsphere 인덱스입니다. 2dsphere 인덱스는 지구 표면(구 형태)을 모형화하여, GeoJSON 객체 또는 좌표 쌍으로 지정된 도형(점, 선, 다각형 등)에 대한 공간 연산을 효율적으로 처리할 수 있도록 지원합니다.

주요 특성은 아래와 같습니다:

  1. 구(球) 형태의 좌표 계산

    • 2dsphere 인덱스는 “경도(Longitude), 위도(Latitude)”로 표현되는 WGS84(World Geodetic System 1984) 좌표계나 GeoJSON 좌표계를 사용합니다.
    • 거리 계산 등에 실제 지구 구면을 기반으로 한 근사값을 활용합니다.
  2. 다양한 GeoJSON 형태 지원

    • Point (점)
    • LineString (선)
    • Polygon (다각형)
    • MultiPoint, MultiLineString, MultiPolygon
    • GeometryCollection
    • 이러한 도형들 간의 관계(포함 여부, 교차 여부, 근접도 등)를 매우 빠르게 계산할 수 있습니다.
  3. 지리적 연산을 위한 전용 쿼리 연산자 제공

    • $near, $geoWithin, $geoIntersects, $geoNear(집계 파이프라인) 등을 통해, 반경 내 위치 검색, 특정 영역(폴리곤 등)에 포함된 문서 검색, 도형 간 교차 여부 등을 빠르게 검색하고 계산할 수 있습니다.

2dsphere 인덱스 생성 방법

인덱스를 생성할 필드 구조

일반적으로 2dsphere 인덱스를 생성하려면, 해당 필드에는 GeoJSON 형태 또는 [경도, 위도] 배열 형식이 저장되어 있어야 합니다. 예시로 location 필드에 Point 타입의 GeoJSON을 저장한다고 하면 아래와 같은 형태가 일반적입니다:

{
  "_id": 1,
  "name": "Seoul City Hall",
  "location": {
    "type": "Point",
    "coordinates": [126.9779692, 37.566535]
  }
}

MongoDB에서 좌표는 [경도, 위도] 순서여야 합니다.

2dsphere 인덱스 생성 예시

location 필드를 2dsphere 인덱스로 생성하려면 아래 명령어를 사용합니다:

db.collection.createIndex(
  { location: "2dsphere" }
);

또한 다른 필드와 결합하여 복합 인덱스를 생성할 수도 있습니다. 예를 들어, category 필드와 함께 만들 수 있습니다:

db.collection.createIndex(
  { category: 1, location: "2dsphere" }
);

이렇게 하면 MongoDB가 category로 먼저 정렬한 뒤, 동일한 범주 내에서 location 지리 쿼리를 효율적으로 수행할 수 있습니다.


2dsphere 인덱스를 활용한 쿼리

2dsphere 인덱스를 사용하면, MongoDB에서 제공하는 지리 공간 전용 연산자를 통해 지리적 연산을 빠르게 처리할 수 있습니다.

1. $near

예시) 서울시청(coordinates: [126.9779692, 37.566535])을 기준으로 2km 이내에 있는 문서를 검색하고, 가까운 순으로 정렬하는 쿼리:

db.collection.find({
  location: {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: [126.9779692, 37.566535]
      },
      $maxDistance: 2000 // 단위: 미터
    }
  }
});

2. $geoWithin

예시) 아래는 [126.90, 37.55] ~ [127.10, 37.70] 범위의 사각형 내부에 위치한 문서를 검색하는 쿼리입니다:

db.collection.find({
  location: {
    $geoWithin: {
      $geometry: {
        type: "Polygon",
        coordinates: [
          [
            [126.90, 37.55], 
            [127.10, 37.55], 
            [127.10, 37.70], 
            [126.90, 37.70], 
            [126.90, 37.55]
          ]
        ]
      }
    }
  }
});

3. $geoIntersects

예시) region 필드에 저장된 폴리곤이, 아래 정의된 폴리곤과 교차하는지 여부를 판별하는 쿼리:

db.collection.find({
  region: {
    $geoIntersects: {
      $geometry: {
        type: "Polygon",
        coordinates: [
          [
            [126.90, 37.55],
            [127.00, 37.55],
            [127.00, 37.65],
            [126.90, 37.65],
            [126.90, 37.55]
          ]
        ]
      }
    }
  }
});

$geoNear (Aggregation Pipeline)

$geoNear집계(Aggregation) 파이프라인에서 사용되는 지리 공간 연산 단계입니다. $near 쿼리와 유사하게 거리에 대한 계산 및 정렬을 수행하지만, 집계 파이프라인과 결합해 보다 복잡한 분석이나 후속 처리를 쉽게 구현할 수 있습니다.

사용 방법

db.collection.aggregate([
  {
    $geoNear: {
      near: {
        type: "Point",
        coordinates: [126.9779692, 37.566535]
      },
      distanceField: "dist.calculated",
      maxDistance: 2000,        // 선택 사항 (미터 단위)
      query: { category: "cafe" }, // 다른 필드에 대한 필터 조건 가능
      spherical: true             // 구면(지구) 계산 사용 여부
    }
  },
  {
    $project: {
      name: 1,
      "dist.calculated": 1
    }
  }
]);

이렇게 $geoNear를 활용하면, 거리 정보를 계산해 문서에 추가한 뒤, 후속 $match, $group, $project 등의 파이프라인 스테이지로 다양한 분석을 수행할 수 있습니다.


주의사항 및 베스트 프랙티스

  1. 인덱스 생성 시 좌표 순서 준수
    • [경도, 위도] 순서를 잘못 지정하면 의도와 다른 결과가 나올 수 있습니다.
  2. 필드 데이터 형식
    • location.typePoint, Polygon 등 유효한 GeoJSON 타입인지 확인합니다.
    • coordinates 배열도 올바른 구조를 갖춰야 합니다. (특히 Polygon의 경우 폐곡선으로 정의)
  3. 복합 인덱스 사용 시 고려 사항
    • 지리 공간 인덱스와 일반(1, -1) 인덱스를 함께 쓸 때는 쿼리 패턴을 주의 깊게 살펴봐야 합니다.
    • 예를 들어 { category: 1, location: "2dsphere" }를 만들면, 쿼리 시 category를 먼저 선별한 뒤 location 지리 쿼리를 수행합니다.
    • 지리 연산과 범위 쿼리가 함께 있을 때는 쿼리 계획(Explain)을 통해 성능을 점검해야 합니다.
  4. 지리적 범위 제한(쿼리 스캔 최소화)
    • $near 혹은 $geoNear 사용 시, $maxDistance 혹은 maxDistance를 설정하지 않으면 스캔 범위가 커져 성능 저하가 발생할 수 있습니다.
    • 가능한 구체적인 반경 또는 쿼리 조건을 설정하여 효율을 높이는 것이 좋습니다.
  5. 정확도 vs 성능
    • 2dsphere 인덱스는 실제 GIS(지리 정보 시스템)에 비해 오차 범위가 있는 근사 계산을 합니다.
    • 그러나 대부분의 위치 기반 서비스(O2O, 지도, 매장 찾기 등)에는 충분한 정확도를 제공하므로, 요구사항에 맞추어 사용하면 됩니다.

예시 시나리오

  1. 매장 위치 검색

    • 사용자 현재 위치에서 가장 가까운 매장 10곳 찾기
    • $near 또는 $geoNear 사용 + $limit 설정
  2. 택시/배달 서비스

    • 드라이버(또는 라이더)의 위치를 실시간으로 저장하고, 사용자 주변 드라이버를 검색
    • $geoWithin으로 구역 기반 필터, $near 또는 $geoNear로 거리 기반 정렬
  3. 지역 경계 기반 통계

    • Polygon으로 행정 구역을 저장하고, $geoWithin을 통해 사용자가 속한 행정 구역을 판별
    • $geoIntersects로 교차 여부 분석(예: 이벤트 반경과 행정 구역이 얼마나 겹치는지)
  4. 데이터 후처리/분석

    • $geoNear를 Aggregation Pipeline에 결합하여, 검색된 데이터에 대한 추가 통계나 그룹 연산을 수행

결론

MongoDB의 2dsphere 인덱스는 지구 표면을 다루는 다양한 위치 기반 애플리케이션에서 핵심적으로 사용됩니다.

복잡한 분석이 필요한 경우 $geoNear 단계와 Aggregation Pipeline을 결합해 다양하게 확장할 수 있습니다. 실무에서는 데이터 분포와 실제 쿼리 패턴에 따라 지리 인덱스를 어떻게 설계할지, 쿼리를 어떻게 최적화할지를 고려하는 것이 중요합니다.


참고 자료

위 문서는 MongoDB 2dsphere 인덱스 및 $geoNear를 포함한 지리 공간 쿼리에 대한 핵심 개념과 사용법을 정리한 것으로, 실제 프로젝트 적용 시에는 더 상세한 데이터 모델링, 샤딩, 성능 튜닝 전략 등을 함께 고려하시기 바랍니다.


Troubleshooting